package ar.edu.utn.frba.proyecto.grupo101;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import junit.framework.TestCase;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;

import ar.com.siavu.controller.configuration.HibernateUtil;
import ar.com.siavu.controller.memory.AGSingleton;
import ar.com.siavu.domain.Actividad;
import ar.com.siavu.domain.ActividadDiaLibre;
import ar.com.siavu.domain.ActividadPosta;
import ar.com.siavu.domain.ETipoPosta;
import ar.com.siavu.domain.ETipoProgramacion;
import ar.com.siavu.domain.ETipoUsuario;
import ar.com.siavu.domain.Plazo;
import ar.com.siavu.domain.Posta;
import ar.com.siavu.domain.Programacion;
import ar.com.siavu.domain.Regla;
import ar.com.siavu.domain.Usuario;

public class PoblacionInicialNueva extends TestCase{

	public void testCargarPoblacionInicial(){
		
		Session s= HibernateUtil.getSessionFactory().getCurrentSession();
		Transaction t= s.beginTransaction();
		
		Criteria critPrep= s.createCriteria(Regla.class);
		List<Regla> reglas=critPrep.list();
		AGSingleton.getInstance().setReglasPost(new HashMap<String, Integer>());
		for (Regla regla : reglas) {
			AGSingleton.getInstance().getReglasPost().put(regla.getNomenclatura(), regla.getHorasPostAterrizaje());
			
		}
		
		Criteria critPlazo= s.createCriteria(Plazo.class);
		critPlazo.add(Restrictions.eq("Mes",9));
		critPlazo.add(Restrictions.eq("Anio",2010));
		Plazo plazo= (Plazo)critPlazo.list().get(0);
		
		Criteria critPosta= s.createCriteria(Posta.class);
		critPosta.createAlias("VueloIda", "vue");
		critPosta.add(Restrictions.in("vue.Numero", new Integer[]{1182,1132,1140,1160,1302,1376,1360,1364}));
		GregorianCalendar gcInicio= new GregorianCalendar();
		gcInicio.set(2010, 9-1, 1, 0, 0);
		GregorianCalendar gcFin= new GregorianCalendar();
		gcFin.set(2010, 9-1, 30, 23, 59);
		critPosta.add(Restrictions.ge("vue.FechaSalida", gcInicio.getTime()));
		critPosta.add(Restrictions.le("vue.FechaSalida", gcFin.getTime()));
		critPosta.addOrder(Order.asc("vue.FechaSalida"));
		List<Posta> postas= critPosta.list();
		
		Criteria crit=s.createCriteria(Usuario.class);
		crit.add(Restrictions.eq("TipoUsuario", 1));
		List<Usuario> jefes= crit.list();
		Map<Usuario, Actividad> actividadesFijas= new LinkedHashMap<Usuario, Actividad>();
		Criteria actividadesFijasCrit = s.createCriteria(ActividadPosta.class);
		actividadesFijasCrit.createAlias("Posta", "post");
		actividadesFijasCrit.createAlias("post.VueloVuelta", "vv");
		actividadesFijasCrit.createAlias("post.VueloIda", "vi");
		actividadesFijasCrit.add(Restrictions.ge("vv.FechaLlegada", gcInicio.getTime()));
		actividadesFijasCrit.add(Restrictions.le("vi.FechaSalida", gcInicio.getTime()));
		actividadesFijasCrit.add(Restrictions.in("vi.Numero", new Integer[]{1182,1132,1140,1160,1302,1376,1360,1364}));
		actividadesFijasCrit.createAlias("Programacion", "prog");
		actividadesFijasCrit.add(Restrictions.eq("prog.TipoProgramacion", ETipoProgramacion.PlanVuelo.getId()));
		
		List<ActividadPosta> auxAct=actividadesFijasCrit.list();
		
		for (ActividadPosta act : auxAct) {
			actividadesFijas.put(act.getProgramacion().getUsuario(), act);			
		}
		
		Criteria preProgCrit= s.createCriteria(Actividad.class);
		preProgCrit.add(Restrictions.between("FechaInicio", gcInicio.getTime(), gcFin.getTime()));
		preProgCrit.createAlias("Programacion", "prog");
		
		preProgCrit.add(Restrictions.eq("prog.TipoProgramacion", ETipoProgramacion.PreProgramacion.getId()));
		preProgCrit.createAlias("prog.Usuario", "usuario");
		preProgCrit.add(Restrictions.eq("usuario.TipoPosta",ETipoPosta.Internacional.getId()));
		preProgCrit.add(Restrictions.eq("usuario.TipoUsuario",ETipoUsuario.JefeCabina.getId()));
		preProgCrit.createAlias("prog.Plazo", "plaz");
		preProgCrit.add(Restrictions.eq("plaz.Mes",9));
		preProgCrit.add(Restrictions.eq("plaz.Anio",2010));
		List<Actividad> actPreProg=preProgCrit.list();
		
		Map<Usuario, List<Actividad>> preProgramaciones= new LinkedHashMap<Usuario, List<Actividad>>();
		Map<Posta, List<Usuario>> programacionPosta= new LinkedHashMap<Posta, List<Usuario>>();
		Map<Usuario, List<Actividad>> programacionUsuario=new LinkedHashMap<Usuario, List<Actividad>>();
		Map<Usuario, Programacion> objetosProgramacion= new LinkedHashMap<Usuario, Programacion>();
		for (Usuario jefe : jefes) {
			preProgramaciones.put(jefe, new ArrayList<Actividad>());
			programacionUsuario.put(jefe, new ArrayList<Actividad>());
			Programacion p= new Programacion();
			p.setActividades(new ArrayList<Actividad>());
			p.setPlazo(plazo);
			p.setTipoProgramacion(ETipoProgramacion.PlanVuelo.getId());
			p.setUsuario(jefe);
			objetosProgramacion.put(jefe, p);
		}
		for (Actividad actividad : actPreProg) {
			preProgramaciones.get(actividad.getProgramacion().getUsuario()).add(actividad);
		}
		for (Entry<Usuario, List<Actividad>> actividades : preProgramaciones.entrySet()) {
			Collections.sort(actividades.getValue());			
		}
		
		for (Posta posta : postas) {
			programacionPosta.put(posta, new ArrayList<Usuario>());
		}
		for (Posta posta : postas) {
			int cantidad=1;
			//TODO: Arreglar esto
			//int cantidad=Math.max(posta.getVueloIda().getAvion().getCantidadJefesCabinaMinimo(), posta.getVueloVuelta().getAvion().getCantidadJefesCabinaMinimo());
			List<Usuario> usuariosSeleccionPosta=getUsuariosSeleccionPosta(posta, preProgramaciones, programacionUsuario, actividadesFijas,cantidad);
			if (usuariosSeleccionPosta.size()< cantidad){
				throw new RuntimeException("No alcanza la cantidad de usuarios para los vuelos");
			}
			for (Usuario usuario : usuariosSeleccionPosta) {
				ActividadPosta act= new ActividadPosta();
				act.setDescripcion(posta.getNombre());
				act.setFechaInicio(posta.getVueloIda().getFechaSalida());
				act.setFechaFin(posta.getVueloVuelta().getFechaLlegada());
				act.setPosta(posta);
				act.setProgramacion(objetosProgramacion.get(usuario));
				programacionUsuario.get(usuario).add(act);
				objetosProgramacion.get(usuario).getActividades().add(act);
				if (programacionUsuario.get(usuario).size()==1){
					List<Actividad> diasLibres= getDiasLibresUsuario(usuario, preProgramaciones,actividadesFijas.get(usuario), programacionUsuario);
					for (Actividad actividad : diasLibres) {
						actividad.setProgramacion(objetosProgramacion.get(usuario));
						programacionUsuario.get(usuario).add(actividad);
						objetosProgramacion.get(usuario).getActividades().add(actividad);
					}
				}
			}			
		}
		
		SimpleDateFormat sdf= new SimpleDateFormat("dd.MM");
		for (Usuario usuario : jefes) {
			System.out.println(usuario.getNombreCompleto());
			Collections.sort(objetosProgramacion.get(usuario).getActividades());
			System.out.print("Prog ");
			for (Actividad actividad : objetosProgramacion.get(usuario).getActividades()) {
				System.out.print(actividad.getDescripcion() + " " + sdf.format(actividad.getFechaInicio()) + "|" + sdf.format(actividad.getFechaFin()) + "|");
				
			}
			System.out.println();
			System.out.println("FV: " + (preProgramaciones.get(usuario).get(0).getProgramacion().getFechasVariables()? "SI": "NO"));
			System.out.print("Prep ");
			for (Actividad actividad : preProgramaciones.get(usuario)) {
				String desc="";
				if (actividad instanceof ActividadPosta){
					ActividadPosta actp= (ActividadPosta) actividad;
					desc= actp.getPosta().getNombre();
				}
				if (actividad instanceof ActividadDiaLibre){
					desc= "DL";
				}
				System.out.print(desc + " " + sdf.format(actividad.getFechaInicio()) + "|" + sdf.format(actividad.getFechaFin()) + "|");
				
			}
			System.out.println();
			System.out.println();
		}
		//t.commit();
	}

	private List<Actividad> getDiasLibresUsuario(Usuario usuario,
			Map<Usuario, List<Actividad>> preProgramaciones,
			Actividad actividadFija, Map<Usuario, List<Actividad>> programacionUsuario) {
		List<Actividad> result= new ArrayList<Actividad>();
		List<Actividad> preProgActual= preProgramaciones.get(usuario);
		Collections.sort(preProgActual);
		ActividadDiaLibre pedido =null;
		for (Actividad actividad : preProgActual) {
			if (actividad instanceof ActividadDiaLibre){
				pedido=(ActividadDiaLibre)actividad;
				break;
			}
		}
		procesoDiaLibre(usuario, actividadFija, programacionUsuario, result,
				pedido);

		GregorianCalendar adelante=new GregorianCalendar();
		adelante.setTime(pedido.getFechaInicio());
		GregorianCalendar atras=new GregorianCalendar();
		atras.setTime(pedido.getFechaInicio());
		while(result.isEmpty()){
			
			
			adelante.add(GregorianCalendar.DAY_OF_MONTH, 1);
			atras.add(GregorianCalendar.DAY_OF_MONTH, -1);
			if (adelante.get(GregorianCalendar.DAY_OF_MONTH) <26){
				pedido.setFechaInicio(adelante.getTime());
				procesoDiaLibre(usuario, actividadFija, programacionUsuario, result,
						pedido);
			}
			if (result.isEmpty()&& atras.get(GregorianCalendar.DAY_OF_MONTH)>=1){
				pedido.setFechaInicio(atras.getTime());
				procesoDiaLibre(usuario, actividadFija, programacionUsuario, result,
						pedido);
			}
		}
		// TODO Auto-generated method stub
		return result;
	}

	private void procesoDiaLibre(Usuario usuario, Actividad actividadFija,
			Map<Usuario, List<Actividad>> programacionUsuario,
			List<Actividad> result, ActividadDiaLibre pedido) {
		GregorianCalendar gcInicio= new GregorianCalendar();
		gcInicio.setTime(pedido.getFechaInicio());
		GregorianCalendar gcFin= new GregorianCalendar();
		gcFin.setTime(pedido.getFechaFin());
		gcFin.add(GregorianCalendar.DAY_OF_MONTH, 3);
		if (validaFechasDiasLibresContraActividadesActuales(gcInicio.getTime(), gcFin.getTime(), programacionUsuario.get(usuario), actividadFija)
				&&validaFechasDiasLibresContraActividadFija(gcInicio.getTime(), gcFin.getTime(), actividadFija)){
			for (int i=gcInicio.get(GregorianCalendar.DAY_OF_MONTH); i<gcInicio.get(GregorianCalendar.DAY_OF_MONTH)+4;i++){
				ActividadDiaLibre actdl= new ActividadDiaLibre();
				actdl.setDescripcion("DL");
				GregorianCalendar inicioDL= new GregorianCalendar();
				inicioDL.set(gcInicio.get(GregorianCalendar.YEAR),
						gcInicio.get(GregorianCalendar.MONTH),
						i, 0, 0, 1);
				actdl.setFechaInicio(inicioDL.getTime());
				GregorianCalendar finDL= new GregorianCalendar();
				finDL.set(gcInicio.get(GregorianCalendar.YEAR),
						gcInicio.get(GregorianCalendar.MONTH),
						i, 23, 59, 59);
				actdl.setFechaFin(finDL.getTime());
				result.add(actdl);						
			}
			
		}
	}

	private List<Usuario> getUsuariosSeleccionPosta(Posta posta,
			Map<Usuario, List<Actividad>> preProgramaciones,
			Map<Usuario, List<Actividad>> programacionActual,
			Map<Usuario, Actividad> actividadesFijas,
			int cantidad) {
		List<Actividad> actividadesConPosta= new ArrayList<Actividad>();
		for (Entry<Usuario, List<Actividad>> entry : preProgramaciones.entrySet()) {
			for (Actividad actividad : entry.getValue()) {
				if (actividad instanceof ActividadPosta){
					ActividadPosta actp=(ActividadPosta) actividad;
					if (actp.getPosta().equals(posta)){
						actividadesConPosta.add(actp);
					}
				}
			}
		}
		List<Usuario> usuarioConPedidoFiltrados=filtrarUsuariosParaActividad(actividadesConPosta, actividadesFijas, programacionActual);
		List<Usuario> result= new ArrayList<Usuario>();
		if(!usuarioConPedidoFiltrados.isEmpty()){
			Collections.shuffle(usuarioConPedidoFiltrados);
			if (cantidad<usuarioConPedidoFiltrados.size()){				
				for (int i=0; i<cantidad; i++) {
					result.add(usuarioConPedidoFiltrados.get(i));
					
				}
			} else {
				result.addAll(usuarioConPedidoFiltrados);
			}

		}
		if (result.size()<cantidad){
			List<Usuario> usuariosConFechasVariables=filtrarUsuariosFechasVariables(posta,preProgramaciones, actividadesFijas, programacionActual);
			if (cantidad < usuariosConFechasVariables.size() + result.size()){
				Collections.shuffle(usuariosConFechasVariables);
				int resultSize= result.size();
				for (int i=0; i<cantidad -resultSize; i++) {
					result.add(usuariosConFechasVariables.get(i));
				}
			} else {
				result.addAll(usuariosConFechasVariables);
			}
		}
		if (result.size()<cantidad){
			List<Usuario> usuariosDisponibles=filtrarUsuariosDisponbles(posta,actividadesFijas, programacionActual, preProgramaciones);
			if (cantidad<= usuariosDisponibles.size() - result.size()){
				int resultSize= result.size();
				for (int i=0; i<cantidad -resultSize; i++) {
					result.add(usuariosDisponibles.get(i));
				}
			}
		}
		return result;
	}

	private List<Usuario> filtrarUsuariosDisponbles(Posta posta,
			Map<Usuario, Actividad> actividadesFijas,
			Map<Usuario, List<Actividad>> programacionActual, Map<Usuario, List<Actividad>> preProgramaciones) {
		List<Usuario> result= new ArrayList<Usuario>();
		List<Usuario> candidatos= new ArrayList<Usuario>();
		List<Usuario> conDiaLibre= new ArrayList<Usuario>();
		for (Entry<Usuario, List<Actividad>> entry : programacionActual.entrySet()) {
			if (validaActividadContraActividadesActuales(posta, entry.getValue()) && validaActividadContraActividadFija(posta, actividadesFijas.get(entry.getKey()))){
				candidatos.add(entry.getKey());
			}
			
		}
		for (Usuario usuario : candidatos) {
			if (validaPostaContraDiaLibre(posta, preProgramaciones.get(usuario))){
				result.add(usuario);
			} else {
				conDiaLibre.add(usuario); 
			}
			Collections.shuffle(result);
			Collections.shuffle(conDiaLibre);
			result.addAll(conDiaLibre);
		}
		return result;
	}

	private boolean validaPostaContraDiaLibre(Posta posta, List<Actividad> list) {
		GregorianCalendar gcFinPosta= new GregorianCalendar();
		gcFinPosta.setTime(posta.getVueloVuelta().getFechaLlegada());
		gcFinPosta.add(GregorianCalendar.HOUR_OF_DAY, AGSingleton.getInstance().getReglasPost().get(posta.getNombre())-24);
		for (Actividad actividad : list) {
			if (actividad instanceof ActividadDiaLibre){
				ActividadDiaLibre actdl= (ActividadDiaLibre) actividad;
				if (actdl.getFechaInicio().after(posta.getVueloIda().getFechaSalida()) && actdl.getFechaInicio().before(gcFinPosta.getTime())){
					return false;
				}
				if(actdl.getFechaFin().after(posta.getVueloIda().getFechaSalida()) && actdl.getFechaFin().before(gcFinPosta.getTime())){
					return false;
				} 
			}
		}
		return true;
	}

	private List<Usuario> filtrarUsuariosFechasVariables(Posta posta,
			Map<Usuario, List<Actividad>> preProgramaciones,
			Map<Usuario, Actividad> actividadesFijas,
			Map<Usuario, List<Actividad>> programacionActual) {
		List<Usuario> result= new ArrayList<Usuario>();
		for (Entry<Usuario, List<Actividad>> entry : preProgramaciones.entrySet()) {
			Usuario usuario= entry.getKey();
			for (Actividad actividad : entry.getValue()) {
				if (actividad.getProgramacion().getFechasVariables() && actividad instanceof ActividadPosta){
					ActividadPosta actPosta=(ActividadPosta) actividad;
					if ((!actPosta.getPosta().equals(posta)) && actPosta.getPosta().getNombre().equals(posta.getNombre())){
						if (validaActividadContraActividadFija(posta,actividadesFijas.get(usuario)) && 
								validaActividadContraActividadesActuales(posta, programacionActual.get(usuario))){
							result.add(usuario);
						}
						break;
					}
				}
			}
			
		}
		return result;
	}

	private List<Usuario> filtrarUsuariosParaActividad(
			List<Actividad> actividadesConPosta,
			Map<Usuario, Actividad> actividadesFijas,
			Map<Usuario, List<Actividad>> programacionActual) {
		List<Usuario> result=new ArrayList<Usuario>();
		//TODO: validar el avion y fechas variables.
		for (Actividad actividad : actividadesConPosta) {
			Usuario usuario= actividad.getProgramacion().getUsuario();
			boolean valida=validaActividadContraActividadFija(actividad,actividadesFijas.get(usuario)) && 
			validaActividadContraActividadesActuales(actividad, programacionActual.get(usuario));
			if (valida){
				result.add(usuario);
			}
		}
		return result;
	}

	private boolean validaActividadContraActividadesActuales(
			Actividad actividadActual,
			List<Actividad> programacionActual) {
		GregorianCalendar gcFinActual= new GregorianCalendar();
		gcFinActual.setTime(actividadActual.getFechaFin());
		if (actividadActual instanceof ActividadPosta){
			ActividadPosta actActP=(ActividadPosta) actividadActual;
			gcFinActual.add(GregorianCalendar.HOUR_OF_DAY, AGSingleton.getInstance().getReglasPost().get(actActP.getPosta().getNombre()));
		}
		
		for (Actividad actividadProgramada : programacionActual) {
			GregorianCalendar gcFinProgramada= new GregorianCalendar();
			gcFinProgramada.setTime(actividadProgramada.getFechaFin());
			if (actividadProgramada instanceof ActividadPosta){
				ActividadPosta actFijaP=(ActividadPosta) actividadProgramada;
				gcFinProgramada.add(GregorianCalendar.HOUR_OF_DAY, AGSingleton.getInstance().getReglasPost().get(actFijaP.getPosta().getNombre()));
			}
			if (!(gcFinActual.getTime().before(actividadProgramada.getFechaInicio()) || actividadActual.getFechaInicio().after(gcFinProgramada.getTime()))){
				return false;
			}
		}
		return true;
	}
	private boolean validaFechasDiasLibresContraActividadesActuales(
			Date inicio, Date fin,
			List<Actividad> programacionActual, Actividad actividadFija) {
		
		
		for (Actividad actividadProgramada : programacionActual) {
			GregorianCalendar gcFinProgramada= new GregorianCalendar();
			gcFinProgramada.setTime(actividadProgramada.getFechaFin());
			if (actividadProgramada instanceof ActividadPosta){
				ActividadPosta actFijaP=(ActividadPosta) actividadProgramada;
				gcFinProgramada.add(GregorianCalendar.HOUR_OF_DAY, AGSingleton.getInstance().getReglasPost().get(actFijaP.getPosta().getNombre())-24);
			}
			if (!(fin.before(actividadProgramada.getFechaInicio()) || inicio.after(gcFinProgramada.getTime()))){
				return false;
			}
		}
		return true;
	}

	private boolean validaActividadContraActividadFija(Actividad actividadActual,
			Actividad actividadFija) {
		if (actividadFija==null){
			return true;
		}
		GregorianCalendar gc= new GregorianCalendar();
		gc.setTime(actividadFija.getFechaFin());
		
		if (actividadFija instanceof ActividadPosta){
			ActividadPosta actFijaP=(ActividadPosta) actividadFija;
			gc.add(GregorianCalendar.HOUR_OF_DAY, AGSingleton.getInstance().getReglasPost().get(actFijaP.getPosta().getNombre()));
			
		}
		GregorianCalendar gcActual= new GregorianCalendar();
		gcActual.setTime(actividadActual.getFechaInicio());
		if (gcActual.after(gc)){
			return true;
		} else {
			return false;
		}
	}
	private boolean validaFechasDiasLibresContraActividadFija(Date inicio, Date fin,
			Actividad actividadFija) {
		if (actividadFija==null){
			return true;
		}
		GregorianCalendar gc= new GregorianCalendar();
		gc.setTime(actividadFija.getFechaFin());
		
		if (actividadFija instanceof ActividadPosta){
			ActividadPosta actFijaP=(ActividadPosta) actividadFija;
			gc.add(GregorianCalendar.HOUR_OF_DAY, AGSingleton.getInstance().getReglasPost().get(actFijaP.getPosta().getNombre())-24);
			
		}
		GregorianCalendar gcActual= new GregorianCalendar();
		gcActual.setTime(inicio);
		if (gcActual.after(gc)){
			return true;
		} else {
			return false;
		}
	}
	
	private boolean validaActividadContraActividadFija(Posta posta,
			Actividad actividadFija) {
		if (actividadFija==null){
			return true;
		}
		GregorianCalendar gc= new GregorianCalendar();
		gc.setTime(actividadFija.getFechaFin());
		if (actividadFija instanceof ActividadPosta){
			ActividadPosta actFijaP=(ActividadPosta) actividadFija;
			gc.add(GregorianCalendar.HOUR_OF_DAY, AGSingleton.getInstance().getReglasPost().get(actFijaP.getPosta().getNombre()));
			
		}
		GregorianCalendar gcActual= new GregorianCalendar();
		gcActual.setTime(posta.getVueloIda().getFechaSalida());
		if (gcActual.after(gc)){
			return true;
		} else {
			return false;
		}
	}
	
	private boolean validaActividadContraActividadesActuales(
			Posta postaActual,
			List<Actividad> programacionActual) {
		GregorianCalendar gcFinActual= new GregorianCalendar();
		gcFinActual.setTime(postaActual.getVueloVuelta().getFechaLlegada());
		gcFinActual.add(GregorianCalendar.HOUR_OF_DAY, AGSingleton.getInstance().getReglasPost().get(postaActual.getNombre()));
		
		
		for (Actividad actividadProgramada : programacionActual) {
			GregorianCalendar gcFinProgramada= new GregorianCalendar();
			gcFinProgramada.setTime(actividadProgramada.getFechaFin());
			if (actividadProgramada instanceof ActividadPosta){
				ActividadPosta actFijaP=(ActividadPosta) actividadProgramada;
				gcFinProgramada.add(GregorianCalendar.HOUR_OF_DAY, AGSingleton.getInstance().getReglasPost().get(actFijaP.getPosta().getNombre()));
			}
			if (!(gcFinActual.getTime().before(actividadProgramada.getFechaInicio()) || postaActual.getVueloIda().getFechaSalida().after(gcFinProgramada.getTime()))){
				return false;
			}
		}
		return true;
	}
}
